package main

/*----------- 这是一个用七牛云备份SDK所做的云备份程序 ----------
 *                       Author : HackerZ
 *                       Time   : 2015/10/08
 *-------------------------------------------------------------
 */

import (
	// "encoding/json"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"runtime/debug"

	"golang.org/x/net/context"
	. "qiniupkg.com/api.v7/conf"
	"qiniupkg.com/api.v7/kodo"
)

// 配置项
const (
	// 配置变量
	SPACENAME    = "android-finaman-cloud-backup-hackerz" // 七牛空间名
	ListDir      = 0x0001
	TEMPLATE_DIR = "./views"
)

var templates = make(map[string]*template.Template)

// 统一加载所有页面，提高性能
func init() {
	fileInfoArr, err := ioutil.ReadDir(TEMPLATE_DIR)
	check(err)
	var templateName, templatePath string
	for _, fileInfo := range fileInfoArr {
		templateName = fileInfo.Name()
		if ext := path.Ext(templateName); ext != ".html" {
			continue
		}
		templatePath = TEMPLATE_DIR + "/" + templateName
		log.Println("Loading template:", templatePath)
		t := template.Must(template.ParseFiles(templatePath))
		templates[templateName] = t
	}
}

// 初始化七牛云存储操作
func qiniu_init() {

}

// 统一捕获 50x 系列的服务端内部错误
func check(err error) {
	if err != nil {
		panic(err)
	}
}

// 统一渲染页面方法
func renderHtml(w http.ResponseWriter, tmpl string, locals map[string]interface{}) {
	tmpl += ".html"
	err := templates[tmpl].Execute(w, locals)
	check(err)
}

// 上传文件
func upload_qiniu(filename string) {
	// 初始化上传参数
	kodo.SetMac(ACCESS_KEY, SECRET_KEY)
	zone := 0 // 空间(Bucket)所在的区域
	c := kodo.New(zone, nil)

	bucket := c.Bucket(SPACENAME)
	ctx := context.Background()

	// 本地文件上传测试
	localFile := "./uploads/test.jpg"
	err := bucket.PutFile(ctx, nil, filename, localFile, nil)

	if err != nil {
		//  ... 上传失败处理
		log.Println("上传文件失败！")
		return
	}
	//  ... 文件上传成功
	log.Println("上传文件成功~~~~")
	//  除了上传文件，还有CURD操作：
	//
	//  1. entry, err := bucket.Stat(ctx, "foo/bar.jpg") // 看看空间中是否存在某个文件，其属性是什么
	//  2. bucket.Delete(ctx, "foo/bar.jpg") // 删除空间中的某个文件
	//  3. bucket.ChangeMime(ctx, "foo/bar.jpg", "image/jpeg") // 修改某个文件的 MIME 属性
	//  4. bucket.Move(ctx, "foo/bar.jpg", "new-name.jpg") // 移动文件
	//  5. bucket.Copy(ctx, "foo/bar.jpg", "new-copy-file.jpg") // 复制文件
}

// 上传图片页面
func uploadHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		log.Println("This is GET Method")
		renderHtml(w, "upload", nil)
	}
	if r.Method == "POST" {
		log.Println("This is POST Method")
		f, h, err := r.FormFile("files")
		check(err)
		filename := h.Filename
		defer f.Close()
		// 上传文件到七牛云空间
		upload_qiniu(filename)

		// 上传动作完成之后重定向网页
		http.Redirect(w, r, "/upload", http.StatusFound)
	}
}

// 主页(获取七牛云存储空间 list)
func listHandler(w http.ResponseWriter, r *http.Request) {
	// 通过查看七牛的API，它是通过拼接URL去获取数据，如：
	// http://rsf.qbox.me/list?bucket=gitwiduu&limit=20
	// 而它的API接口为 ：List(Context, prefix, marker, limit)(暴露) 和 makeListURL(非暴露)

	// 初始化参数
	kodo.SetMac(ACCESS_KEY, SECRET_KEY)
	zone := 0 // 空间(Bucket)所在的区域
	c := kodo.New(zone, nil)

	bucket := c.Bucket(SPACENAME)
	ctx := context.Background()

	// list_st返回的是Golang中的StructTag数据类型
	list_st, _, _ := bucket.List(ctx, "", "", 20)

	locals := make(map[string]interface{})
	files := []string{}
	for _, fileInfo := range list_st {
		files = append(files, fileInfo.Key)
	}
	locals["files"] = files

	renderHtml(w, "list", locals)

	// 请求URL拼接后应为：
	// http://rsf.qbox.me/list?bucket=android-finaman-cloud-backup-hackerz&limit=20
}

// 删除文件操作
func delHandler(w http.ResponseWriter, r *http.Request) {
	// 获取文件id
	fileId := r.FormValue("id")

	// 初始化参数
	kodo.SetMac(ACCESS_KEY, SECRET_KEY)
	zone := 0 // 空间(Bucket)所在的区域
	c := kodo.New(zone, nil)

	bucket := c.Bucket(SPACENAME)
	ctx := context.Background()

	// 进行删除操作
	err := bucket.Delete(ctx, fileId)
	check(err)
	log.Println("删除成功！")
	http.Redirect(w, r, "/", http.StatusFound)
}

// 利用闭包避免程序运行出错时崩溃
// 将业务处理函数作为参数传入，当错误处理流程
//向上回溯的时候能把错误拦截，避免程序停止运行
func safeHandler(fn http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if e, ok := recover().(error); ok {
				http.Error(w, e.Error(), http.StatusInternalServerError)

				// 或者输出自定义的 50x 错误页面
				// w.WriteHeader(http.StatusInternalServerError)
				// renderHtml(w, "error", e.Error())

				// logging
				log.Println("WARN: panic fired in %v.panic - %v", fn, e)
				log.Println(string(debug.Stack()))
			}
		}()
		fn(w, r)
	}
}

// 加载静态资源，如：CSS、JS
func staticDirHandler(mux *http.ServeMux, prefix string, staticDir string, flags int) {
	mux.HandleFunc(prefix, func(w http.ResponseWriter, r *http.Request) {
		file := staticDir + r.URL.Path[len(prefix)-1:]
		if (flags & ListDir) == 0 {
			fi, err := os.Stat(file)
			if err != nil || fi.IsDir() {
				http.NotFound(w, r)
				return
			}
		}
		http.ServeFile(w, r, file)
	})
}

func main() {

	ACCESS_KEY = "m_xn4k8rHFoXmjYC0z02X-nm5yFaIAy5dw-S4Vcu"
	SECRET_KEY = "IYKDGVTahKPIxe4kJpmxQZ48FIpKrZHEUc7crLco"

	// 配置你自己的 AK 和 SK
	// ACCESS_KEY = "<ACCESS_KEY>"
	// SECRET_KEY = "<SECRET_KEY>"
	mux := http.NewServeMux()
	staticDirHandler(mux, "/assets/", "./public", 0)
	// 在主函数中注册相关业务方法
	mux.HandleFunc("/", safeHandler(listHandler))
	mux.HandleFunc("/upload", safeHandler(uploadHandler))
	mux.Handle("/delete", safeHandler(delHandler))
	// 监听相关端口
	err := http.ListenAndServe(":8080", mux)
	if err != nil {
		log.Fatal("ListenAndServe: ", err.Error())
	}
}
